package com.ljlspring.framework;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;


/**
 * ClassName：BeanFactory
 * Description：IOC简单实现
 * Create by root
 * Date：2020/8/22 13:00
 */
public class BeanFactory {

    private Map<String, Object> ioc = new ConcurrentHashMap<>();

    public BeanFactory(String xml) {
        parseXml(xml);
    }

    /**
     * 借助dom4j解析xml
     * 解析spring配置文件
     * 反射实例化类后存放在ioc中
     *
     * @param xml
     */
    private void parseXml(String xml) {
        try {
            SAXReader reader = new SAXReader();
            String directory = this.getClass().getResource("/") + xml;

            Document document = reader.read(directory);

            //存储bean标签中含有property子标签的元素
            List<Element> beanHasPropertyList = new ArrayList<>();

            //获取beans 标签
            Element rootElement = document.getRootElement();

            //获取bean标签实例化所有bean放入IOC中
            Iterator<Element> beansEleIt = rootElement.elementIterator();

            //把所有bean注册进ioc中
            registerBean(beanHasPropertyList, beansEleIt);

            //解析bean标签的子标签property
            parseXmlProperty(beanHasPropertyList);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 扫描xml中的bean标签,全部实例化存放于IOC中
     *
     * @param beanHasPropertyList
     * @param beansEleIt
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws ClassNotFoundException
     */
    private void registerBean(List<Element> beanHasPropertyList, Iterator<Element> beansEleIt) throws InstantiationException,
            IllegalAccessException, ClassNotFoundException {
        while (beansEleIt.hasNext()) {
            Element beanEle = beansEleIt.next();

            //获取bean标签的属性
            Attribute idAttr = beanEle.attribute("id");
            Attribute classAttr = beanEle.attribute("class");

            Object instance = Class.forName(classAttr.getValue()).newInstance();

            //放入IOC中
            ioc.put(idAttr.getValue(), instance);

            //含有property标签则添加
            if (beanEle.elements().size() > 0) {
                beanHasPropertyList.add(beanEle);
            }
        }
    }

    /**
     * 解析bean标签下的子标签
     * <property></property>
     *
     * @param beanHasPropertyList
     * @throws IllegalAccessException
     */
    private void parseXmlProperty(List<Element> beanHasPropertyList) throws Exception {
        for (Element beanHasProperty : beanHasPropertyList) {
            //获取bean标签的属性
            Attribute idAttr = beanHasProperty.attribute("id");

            //从ioc中获取bean
            Object instance = ioc.get(idAttr.getValue());

            //获取bean标签的子标签property
            List<Element> propertyList = beanHasProperty.elements("property");

            //构造参数标签
            List<Element> constructorList = beanHasProperty.elements("constructor-arg");

            if(propertyList.size() > 0){
                //处理property标签并注入值
                processPropertyList(instance, propertyList);
            }else if(constructorList.size()>0){
                processConstruct(idAttr, instance, constructorList);
            }else{
                throw new RuntimeException("未知的标签,暂不支持");
            }

        }
    }

    /**
     * 处理构造函数标签注入bean
     * @param idAttr
     * @param instance
     * @param constructorList
     * @throws InstantiationException
     * @throws IllegalAccessException
     * @throws java.lang.reflect.InvocationTargetException
     */
    private void processConstruct(Attribute idAttr, Object instance, List<Element> constructorList)
            throws Exception {
        List<Object> allInjectionObj = new ArrayList();
        for (Element construct : constructorList) {
            Attribute ref = construct.attribute("ref");
            Object injectionObject = ioc.get(ref.getValue());
            checkInjectionBeanNotNull(ref, injectionObject);
            allInjectionObj.add(injectionObject);
        }
        Constructor<?>[] declaredConstructors = instance.getClass().getDeclaredConstructors();

        for (Constructor<?> declaredConstructor : declaredConstructors) {

            Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
            if(parameterTypes.length == allInjectionObj.size()){

                for (int i = 0; i < allInjectionObj.size(); i++) {
                    Class aClass = allInjectionObj.get(i).getClass();
                    Class<?> parameterType = parameterTypes[i];

                    Class realClass = null;
                    Class[] interfaces = aClass.getInterfaces();
                    for (Class anInterface : interfaces) {
                        if(anInterface == parameterType){
                            realClass = anInterface;
                            break;
                        }
                    }

                    if(realClass != null){
                        //实例化bean后重新注册
                        Object o = declaredConstructor.newInstance(allInjectionObj.toArray());
                        ioc.put(idAttr.getValue(),o);
                    }

                }
            }
        }
    }

    private void processPropertyList(Object instance, List<Element> propertyList) throws IllegalAccessException {
        Field[] declaredFields = instance.getClass().getDeclaredFields();
        for (Element element : propertyList) {
            Attribute name = element.attribute("name");
            Attribute ref = element.attribute("ref");
            //获取注入bean值
            Object injectionBean = ioc.get(ref.getValue());

            Field declaredField = getInstanceField(declaredFields, name);

            //为null就报错
            checkFieldNotNull(name, declaredField);

            //为null就报错
            checkInjectionBeanNotNull(ref, injectionBean);

            declaredField.setAccessible(true);
            declaredField.set(instance, injectionBean);
        }
    }

    /**
     * 注入值bean不能为null
     *
     * @param ref
     * @param injectionBean
     */
    private void checkInjectionBeanNotNull(Attribute ref, Object injectionBean) {
        if (injectionBean == null) {
            throw new RuntimeException("找不到名称为：" + ref.getValue() + "的bean，该bean未创建。");
        }
    }

    /**
     * 注入字段为null异常抛出
     *
     * @param name
     * @param declaredField
     */
    private void checkFieldNotNull(Attribute name, Field declaredField) {
        if (declaredField == null) {
            throw new RuntimeException("找不到需要注入的属性：" + name.getValue());
        }
    }

    /**
     * 获取指定的属性,用于注入
     *
     * @param declaredFields
     * @param name
     * @return
     */
    private Field getInstanceField(Field[] declaredFields, Attribute name) {
        for (Field declaredField : declaredFields) {
            if (name.getValue().equals(declaredField.getName())) {
                return declaredField;
            }
        }

        return null;
    }


    /**
     * 获取bean
     *
     * @param beanName
     * @return
     */
    public Object getBean(String beanName) {
        return ioc.get(beanName);
    }
}
